I have always been interested in patterns that can be formed through geometry, ever since I could write I would forever be doodling shapes in my school books whilst listening in class. Fast forward 10 years to my current self and I have got the itch to recreate some plots that I had done as a child, but this time trading my pen and paper for a keyboard and mouse. I intend to make ‘art’ using the graphing capabilites in R. This can be done using base R functions, but I will lean towards GGplot, which offers wider adjustments.
There are two lines of art I would like to explore:
These two can be explored independently, but I want a final product that encompasses both of these ideas. To start with, I will show what I mean by randomness. The runif function is able to generate a random number given a max and min value, running the chunk of code will create a unique plot each time it is ran.
library(ggplot2)
plot(runif(10, 1,10), runif(10,1,10))
The idea of randomness can be applied to the colour (commonly named ‘color’ in my code to match American spelling!), pairing that with a few changes to the graph itself, the result is already starting to look mildly ‘arty’.
data <- data.frame(x = runif(1000, 1,10),y = runif(1000,1,10), color = runif(1000, 1,10))
ggplot(data = data)+ geom_point(aes(x = x, y=y, color = color))+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_gradient2()
they say beauty is in the eye of the beholder, but I’m sure that most people who lay eyes upon this plot can agree that a few speckled dots is not groundbreaking art. These points can be changed to different shapes using the ggplot capabilities. However, I want to experiment with points, and create shapes from clusters of points.
For example, a triangle is an easy shape to make. The chunk of code below first generates the centre of the triangle, the three sides of the triangle are constructed independently using the sequence function to create a line of points. This is then repeated for however many triangles have been specified in the first line of code.
initalpoints <- data.frame(x = runif(3, 1, 10), y = runif(3,1,10))
empty_df <- data.frame(x = 0, y=0, color = 0)
for (i in 1:nrow(initalpoints)){
triangleLeft <- data.frame(x= seq(initalpoints[i,1]-2, initalpoints[i,1], 0.01), y= seq(initalpoints[i,2]-1,initalpoints[i,2]+1, 0.01), color = 1)
triangleRight <- data.frame(x= seq(initalpoints[i,1]+2, initalpoints[i,1], -0.01), y= seq(initalpoints[i,2]-1,initalpoints[i,2]+1, 0.01), color = 1)
triangleBottom <- data.frame(x = seq(initalpoints[i,1]-2, initalpoints[i,1]+2, 0.01), y= initalpoints[i,2]-1, color = 1)
empty_df <- rbind(empty_df, triangleLeft)
empty_df <- rbind(empty_df, triangleRight)
empty_df <- rbind(empty_df, triangleBottom)
}
empty_df <- empty_df[-1,]
ggplot(data = empty_df)+ geom_point(aes(x = x, y=y, color = color))+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_gradient2() + xlim(-2,15) + ylim(-2, 15)
The same rule can be applied to create stars (simply a triangle aligned with a flipped version of the same triangle).
initalpoints <- data.frame(x = runif(3, 1, 10), y = runif(3,1,10))
empty_star <- data.frame(x = 0, y=0, color = 0)
for (i in 1:nrow(initalpoints)){
triangleLeft <- data.frame(x= seq(initalpoints[i,1]-3, initalpoints[i,1], 0.01), y= seq(initalpoints[i,2]-1,initalpoints[i,2]+2, 0.01), color = 1)
triangleRight <- data.frame(x= seq(initalpoints[i,1]+3, initalpoints[i,1], -0.01), y= seq(initalpoints[i,2]-1,initalpoints[i,2]+2, 0.01), color = 1)
triangleBottom <- data.frame(x = seq(initalpoints[i,1]-3, initalpoints[i,1]+3, 0.01), y= initalpoints[i,2]-1, color = 1)
UptriangleLeft <- data.frame(x= seq(initalpoints[i,1]-3, initalpoints[i,1], 0.01), y= seq(initalpoints[i,2]+1,initalpoints[i,2]-2, -0.01), color = 1)
UptriangleRight <- data.frame(x= seq(initalpoints[i,1]+3, initalpoints[i,1], -0.01), y= seq(initalpoints[i,2]+1,initalpoints[i,2]-2, -0.01), color = 1)
UptriangleBottom <- data.frame(x = seq(initalpoints[i,1]-3, initalpoints[i,1]+3, 0.01), y= initalpoints[i,2]+1, color = 1)
empty_star <- rbind(empty_star, triangleLeft)
empty_star <- rbind(empty_star, triangleRight)
empty_star <- rbind(empty_star, triangleBottom)
empty_star <- rbind(empty_star, UptriangleBottom)
empty_star <- rbind(empty_star, UptriangleLeft)
empty_star <- rbind(empty_star, UptriangleRight)
}
empty_star <- empty_star[-1,]
ggplot(data = empty_star)+ geom_point(aes(x = x, y=y, color = color))+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_gradient2() + xlim(-2,15) + ylim(-2, 15)
These plots can have similar rules applied to them as before. The size of a triangle can be randomised and the colour of each triangle can be randomly picked. The result is beginning to look like art!
n <- 100
initalpoints <- data.frame(x = runif(n, 1, 10), y = runif(n,1,10), size = runif(n,0.01,0.5))
size <- 0.01
empty_df <- data.frame(x = 0, y=0, color = 0, size = size)
for (i in 1:nrow(initalpoints)){
color <- runif(1, 0, 10)
triangleLeft <- data.frame(x= seq(initalpoints[i,1]-2*initalpoints[i,3], initalpoints[i,1], 0.01), y= seq(initalpoints[i,2]-1*initalpoints[i,3],initalpoints[i,2]+1*initalpoints[i,3], 0.01), color = color, size = size)
triangleRight <- data.frame(x= seq(initalpoints[i,1]+2*initalpoints[i,3], initalpoints[i,1], -0.01), y= seq(initalpoints[i,2]-1*initalpoints[i,3],initalpoints[i,2]+1*initalpoints[i,3], 0.01), color = color, size = size)
triangleBottom <- data.frame(x = seq(initalpoints[i,1]-2*initalpoints[i,3], initalpoints[i,1]+2*initalpoints[i,3], 0.01), y= initalpoints[i,2]-1*initalpoints[i,3], color = color, size = size)
empty_df <- rbind(empty_df, triangleLeft)
empty_df <- rbind(empty_df, triangleRight)
empty_df <- rbind(empty_df, triangleBottom)
}
empty_df <- empty_df[-1,]
ggplot(data = empty_df)+ geom_point(aes(x = x, y=y, color = color), size = size)+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_gradient2() + xlim(-1,12) + ylim(-1, 12)
That is the random aspect of the art done for now, attention is now switched to the geometric side. As a child I would draw a geometric pattern regularly. This involved creating a square out of points equally spaced apart
L <- data.frame(x = 1, y= 1:50)
R <- data.frame(x = 50, y= 1:50)
B <- data.frame(x = 1:50, y= 1)
T <- data.frame(x = 1:50, y= 50)
square <- rbind(L, R, B, T)
plot(square)
This ugly looking plot demonstrates what I mean. From here, the second highest point on the left hand side of the square and the second most left point on the bottom of the square are connected, then the point below the initial point is connected to the point to the right of the second point and so on and so forth. This is then repeated where every point will have two lines connecting it to two other points.
emptySquare <- data.frame(x=0, y=0)
for (i in 1:48){
emptySquare <- rbind(emptySquare, L[50-i,], B[i+1,], R[i+1,], T[50-i,])
}
NewSquare <- emptySquare[2:189,]
NewSquare$Group <- 1:188
row.names(NewSquare) <- NULL
NewSquare[189,] <- c(x=1, y=2, group = 189)
NewSquare[190,] <- c(x=49, y=1, group = 190)
NewSquare[191,] <- c(x=50, y=49, group = 191)
NewSquare[192,] <- c(x=2, y=50, group = 192)
NewSquare[193,] <- c(x=1, y=2, group = 193)
ToStartPattern<- c(x=49, y=50, group = 0)
NewSquare <- rbind(ToStartPattern, NewSquare)
Cube <- data.frame(x =c(1,50,50,1, 1),y=c(1,1,50,50, 1), Group = c(192,193,194,195, 196) )
NewSquare <- rbind(NewSquare, Cube)
BackgroundPlot <- ggplot(data = NewSquare, aes())+ geom_point(aes(x = x, y=y), size = 0.02)+
geom_path(aes(x=x, y=y,color =1 , group = 1))+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_gradient2()
BackgroundPlot
My favourite thing about this image is the apparent curvature of the lines, all lines constructed are straight lines, yet the final product appears to bend perfectly.
This makes for a great frame for some generative art. Luckily, I have some that I created earlier!
BackgroundPlot + geom_point(data = empty_df, aes(x = x, y=y, color = color), size = size)
This plot was not what I was hoping, I wanted the triangles to be scattered across the black empty space, but this was not the case due to the difference in coordinate scales. To remedy this, I can try to calculate a feasible region that I should contain all plots within.
TriangleRange <- data.frame(x=c(25.5,45, 25.5, 5, 25.5), y= c(5,25,45,25,5))
TraingleRangePlot <- geom_path(TriangleRange, mapping = aes(x=x,y=y, group =1, color = 1))
BackgroundPlot + geom_hline(yintercept=25, color = 'red') + geom_hline(yintercept=c(5,45), color = 'red') +
geom_vline(xintercept=c(25.5, 5, 45), color = 'red') +geom_path(TriangleRange, mapping = aes(x=x,y=y, group =1, color = 1))
This plot looks like something out of a spy movie with lasers scanning across the screen, but no, What it does is give me a diamond of feasibility. Frustratingly, as satisfying as the background plot is, it is actually not symmetrical, if you look at the left and right hand side of the white diamond, you can see that they dont perfectly intersect the background, the same goes for the top and bottom.
initalpointsRanged <- data.frame(x = runif(n, (25.5+5)/2, (25.5+45)/2), y = runif(n, (25.5+5)/2, (25.5+45)/2))
size <- 0.01
empty_df <- data.frame(x = 0, y=0, color = 0, size = size)
for (i in 1:nrow(initalpointsRanged)){
color <- runif(1, 0, 10)
triangleLeft <- data.frame(x= seq(initalpointsRanged[i,1]-2, initalpointsRanged[i,1], 0.01), y= seq(initalpointsRanged[i,2]-1,initalpointsRanged[i,2]+1, 0.01), color = color, size = size)
triangleRight <- data.frame(x= seq(initalpointsRanged[i,1]+2, initalpointsRanged[i,1], -0.01), y= seq(initalpointsRanged[i,2]-1,initalpointsRanged[i,2]+1, 0.01), color = color, size = size)
triangleBottom <- data.frame(x = seq(initalpointsRanged[i,1]-2, initalpointsRanged[i,1]+2, 0.01), y= initalpointsRanged[i,2]-1, color = color, size = size)
empty_df <- rbind(empty_df, triangleLeft)
empty_df <- rbind(empty_df, triangleRight)
empty_df <- rbind(empty_df, triangleBottom)
}
empty_df <- empty_df[-1,]
BackgroundPlot + geom_point(data = empty_df, aes(x = x, y=y, color = color), size = size)
It is difficult to fill the blank space perfectly, the conditions I have put on the triangles keep it within a square which is quite clear, but it does not look as I would like it. The diamond shape makes it tricky to fill, for this reason, I will try a different approach on the random aspect of the art.
initialCoords <- data.frame(x=25.5, y = 5, line = 1)
Splits <- 10
i= 1
ToSplit <- rbinom(1, 1, 0.003)
while(ToSplit == 0){
newPoint <- data.frame(x = rnorm(1,tail(initialCoords$x),0.05 ), y =max(initialCoords$y+0.05 ), line = i)
initialCoords <- rbind(initialCoords, newPoint)
ToSplit <- rbinom(1, 1, 0.003)
}
plot(initialCoords$x, initialCoords$y, xlim = c(0,50), ylim= c(0,50))
BackgroundPlot +
geom_point(data = initialCoords, aes(x = x, y=y, color = color), size = 0.5)
I have instead tried to create a tree of sorts. The idea is that points will be generated vertically to create a path. At each point, there is a small (0.5%) chance that the ‘tree’ will split. When this happens the the tree should split and two branches should be generated. It is clear to see from the above plot that this doesn’t necessarily work, it is difficult to create something that actually mimics a tree. For this reason, this idea is not pursued any further.
The final option I want to explore is to utilise pre existing generative code packages. The aRtsy package can generate really interesting generative art with a simple line of code. Now it would not be much if I used this package on its own to create a plot, so I will instead go into the output of the code and take the x and y coordinates of a generative plot. These coordinates can then be scaled to match the background plot and then plotted together. The final output is below.
library(aRtsy)
set.seed(1)
PetriPlot <- canvas_petri(colors = colorPalette("sooph"))
PetriPlot <- canvas_phyllotaxis(colors = colorPalette("tuscany1"))
PetriCoords <- data.frame(x = PetriPlot$data$x + 25, y = PetriPlot$data$y + 25)
NewSquare$x <- (NewSquare$x*4) -100
NewSquare$y <- (NewSquare$y*4) -100
NewSquare$color <- 'color'
PetriCoords <- data.frame(x = PetriPlot$data$x, y = PetriPlot$data$y)
PetriCoords <- PetriCoords*0.6
BackgroundPlot <- ggplot(data = NewSquare, aes())+ geom_point(aes(x = x, y=y), size = 0.02)+
geom_path(aes(x=x, y=y,color = as.factor(color), group = 1))+
theme(axis.title.x=element_blank(),
axis.text.x=element_blank(),
axis.ticks.x=element_blank(),
axis.title.y=element_blank(),
axis.text.y=element_blank(),
axis.ticks.y=element_blank(),
panel.background = element_rect(fill = 'black'),
plot.background=element_rect(fill = "gray"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
legend.position = 'none') +
scale_color_manual(values = c('lightsteelblue', 'floralwhite'))
PetriCoords$color <- 'color1'
BackgroundPlot + geom_point(data = PetriCoords, aes(x = x, y=y, color = as.factor(color)), size = size+0.5)
So there it is, nothing groundbreaking! I don’t think the course of art as we know it will be changed by this piece of work, but for my first time creating art with code, I am happy with the result. I hope this is the first of many pieces I create.